package com.tianhai.db;

import java.nio.ByteBuffer;
import java.util.BitSet;

/**
 * 数据库页式存储的Page类
 * 页大小: 8KB (8192 bytes)
 * 使用位图管理空闲槽位
 */
public class Page {
    // 页大小常量: 8KB
    public static final int PAGE_SIZE = 8192;
    
    // 页头大小 (存储元数据)
    private static final int HEADER_SIZE = 64;
    
    // 每个槽位的大小 (可配置)
    private static final int SLOT_SIZE = 128;
    
    // 最大槽位数量
    private static final int MAX_SLOTS = (PAGE_SIZE - HEADER_SIZE) / SLOT_SIZE;
    
    // 页ID
    private int pageId;
    
    // 底层存储缓冲区
    private ByteBuffer buffer;
    
    // 位图，用于管理空闲槽位 (1表示已占用，0表示空闲)
    private BitSet slotBitmap;
    
    // 当前已使用的槽位数量
    private int usedSlots;
    
    /**
     * 构造函数 - 创建新页
     * @param pageId 页ID
     */
    public Page(int pageId) {
        this.pageId = pageId;
        this.buffer = ByteBuffer.allocate(PAGE_SIZE);
        this.slotBitmap = new BitSet(MAX_SLOTS);
        this.usedSlots = 0;
        initializeHeader();
    }
    
    /**
     * 构造函数 - 从现有数据加载页
     * @param pageId 页ID
     * @param data 页数据
     */
    public Page(int pageId, byte[] data) {
        this(pageId, data, true);
    }
    
    /**
     * 构造函数 - 从现有数据加载页
     * @param pageId 页ID
     * @param data 页数据
     * @param parseHeader 是否解析页头（如果为false，则直接使用原始数据）
     */
    public Page(int pageId, byte[] data, boolean parseHeader) {
        if (data.length != PAGE_SIZE) {
            throw new IllegalArgumentException("页数据大小必须为 " + PAGE_SIZE + " 字节");
        }
        this.pageId = pageId;
        this.buffer = ByteBuffer.wrap(data);
        this.slotBitmap = new BitSet(MAX_SLOTS);
        if (parseHeader) {
            loadHeader();
        } else {
            // 不解析头部，用于直接访问原始数据
            this.usedSlots = 0;
        }
    }
    
    /**
     * 初始化页头
     */
    private void initializeHeader() {
        buffer.position(0);
        buffer.putInt(pageId);           // 偏移0: 页ID
        buffer.putInt(usedSlots);        // 偏移4: 已使用槽位数
        buffer.putInt(MAX_SLOTS);        // 偏移8: 最大槽位数
        buffer.putInt(SLOT_SIZE);        // 偏移12: 槽位大小
        // 偏移16-63: 预留空间
    }
    
    /**
     * 从缓冲区加载页头信息
     */
    private void loadHeader() {
        buffer.position(0);
        this.pageId = buffer.getInt();
        this.usedSlots = buffer.getInt();
        int maxSlots = buffer.getInt();
        int slotSize = buffer.getInt();
        
        // 重建位图
        loadBitmap();
    }
    
    /**
     * 从页数据重建位图
     */
    private void loadBitmap() {
        slotBitmap.clear();
        for (int i = 0; i < MAX_SLOTS; i++) {
            int slotOffset = getSlotOffset(i);
            buffer.position(slotOffset);
            // 检查槽位是否被使用 (简单检查前4字节是否全为0)
            int marker = buffer.getInt();
            if (marker != 0) {
                slotBitmap.set(i);
            }
        }
    }
    
    /**
     * 分配一个空闲槽位
     * @return 槽位索引，如果没有空闲槽位返回-1
     */
    public int allocateSlot() {
        if (usedSlots >= MAX_SLOTS) {
            return -1; // 页已满
        }
        
        // 查找第一个空闲槽位
        int slotIndex = slotBitmap.nextClearBit(0);
        if (slotIndex >= MAX_SLOTS) {
            return -1;
        }
        
        // 标记为已使用
        slotBitmap.set(slotIndex);
        usedSlots++;
        updateHeader();
        
        return slotIndex;
    }
    
    /**
     * 释放一个槽位
     * @param slotIndex 槽位索引
     */
    public void freeSlot(int slotIndex) {
        if (slotIndex < 0 || slotIndex >= MAX_SLOTS) {
            throw new IllegalArgumentException("无效的槽位索引: " + slotIndex);
        }
        
        if (!slotBitmap.get(slotIndex)) {
            return; // 槽位已经是空闲的
        }
        
        // 清空槽位数据
        int offset = getSlotOffset(slotIndex);
        buffer.position(offset);
        for (int i = 0; i < SLOT_SIZE; i++) {
            buffer.put((byte) 0);
        }
        
        // 标记为空闲
        slotBitmap.clear(slotIndex);
        usedSlots--;
        updateHeader();
    }
    
    /**
     * 检查槽位是否被占用
     * @param slotIndex 槽位索引
     * @return true表示已占用，false表示空闲
     */
    public boolean isSlotUsed(int slotIndex) {
        if (slotIndex < 0 || slotIndex >= MAX_SLOTS) {
            throw new IllegalArgumentException("无效的槽位索引: " + slotIndex);
        }
        return slotBitmap.get(slotIndex);
    }
    
    /**
     * 写入数据到指定槽位
     * @param slotIndex 槽位索引
     * @param data 要写入的数据
     * @return 成功返回true，失败返回false
     */
    public boolean writeSlot(int slotIndex, byte[] data) {
        if (slotIndex < 0 || slotIndex >= MAX_SLOTS) {
            throw new IllegalArgumentException("无效的槽位索引: " + slotIndex);
        }
        
        if (data.length > SLOT_SIZE) {
            throw new IllegalArgumentException("数据大小超过槽位大小限制");
        }
        
        int offset = getSlotOffset(slotIndex);
        buffer.position(offset);
        buffer.put(data);
        
        // 如果槽位之前未使用，标记为已使用
        if (!slotBitmap.get(slotIndex)) {
            slotBitmap.set(slotIndex);
            usedSlots++;
            updateHeader();
        }
        
        return true;
    }
    
    /**
     * 从指定槽位读取数据
     * @param slotIndex 槽位索引
     * @return 槽位中的数据
     */
    public byte[] readSlot(int slotIndex) {
        if (slotIndex < 0 || slotIndex >= MAX_SLOTS) {
            throw new IllegalArgumentException("无效的槽位索引: " + slotIndex);
        }
        
        int offset = getSlotOffset(slotIndex);
        byte[] data = new byte[SLOT_SIZE];
        buffer.position(offset);
        buffer.get(data);
        
        return data;
    }
    
    /**
     * 从指定槽位读取指定长度的数据
     * @param slotIndex 槽位索引
     * @param length 要读取的长度
     * @return 读取的数据
     */
    public byte[] readSlot(int slotIndex, int length) {
        if (slotIndex < 0 || slotIndex >= MAX_SLOTS) {
            throw new IllegalArgumentException("无效的槽位索引: " + slotIndex);
        }
        
        if (length > SLOT_SIZE) {
            throw new IllegalArgumentException("读取长度超过槽位大小");
        }
        
        int offset = getSlotOffset(slotIndex);
        byte[] data = new byte[length];
        buffer.position(offset);
        buffer.get(data);
        
        return data;
    }
    
    /**
     * 获取槽位在页中的偏移量
     * @param slotIndex 槽位索引
     * @return 偏移量
     */
    private int getSlotOffset(int slotIndex) {
        return HEADER_SIZE + slotIndex * SLOT_SIZE;
    }
    
    /**
     * 更新页头信息
     */
    private void updateHeader() {
        int currentPos = buffer.position();
        buffer.position(4);
        buffer.putInt(usedSlots);
        buffer.position(currentPos);
    }
    
    /**
     * 获取页的原始数据
     * @return 页数据的字节数组
     */
    public byte[] getData() {
        return buffer.array();
    }
    
    /**
     * 获取底层ByteBuffer
     * @return ByteBuffer
     */
    public ByteBuffer getBuffer() {
        return buffer.asReadOnlyBuffer();
    }
    
    /**
     * 获取页ID
     * @return 页ID
     */
    public int getPageId() {
        return pageId;
    }
    
    /**
     * 获取已使用的槽位数量
     * @return 已使用槽位数
     */
    public int getUsedSlots() {
        return usedSlots;
    }
    
    /**
     * 获取空闲槽位数量
     * @return 空闲槽位数
     */
    public int getFreeSlots() {
        return MAX_SLOTS - usedSlots;
    }
    
    /**
     * 获取最大槽位数量
     * @return 最大槽位数
     */
    public int getMaxSlots() {
        return MAX_SLOTS;
    }
    
    /**
     * 获取槽位大小
     * @return 槽位大小
     */
    public int getSlotSize() {
        return SLOT_SIZE;
    }
    
    /**
     * 检查页是否已满
     * @return true表示已满
     */
    public boolean isFull() {
        return usedSlots >= MAX_SLOTS;
    }
    
    /**
     * 检查页是否为空
     * @return true表示为空
     */
    public boolean isEmpty() {
        return usedSlots == 0;
    }
    
    /**
     * 获取位图的副本
     * @return 位图副本
     */
    public BitSet getBitmap() {
        return (BitSet) slotBitmap.clone();
    }
    
    /**
     * 清空整个页
     */
    public void clear() {
        buffer.clear();
        for (int i = 0; i < PAGE_SIZE; i++) {
            buffer.put((byte) 0);
        }
        slotBitmap.clear();
        usedSlots = 0;
        initializeHeader();
    }
    
    @Override
    public String toString() {
        return String.format("Page[id=%d, usedSlots=%d/%d, freeSlots=%d]", 
            pageId, usedSlots, MAX_SLOTS, getFreeSlots());
    }
}
